﻿/**
 * Peter
 * Created by: Peter Development Team
 *    http://peter.codeplex.com/
 * 
 * GNU General Public License version 2 (GPLv2)
 *    http://peter.codeplex.com/license
 **/

using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Peter.Common.Utilities;
using Peter.Ftp.Data;
using Peter.Ftp.DataAccess;

namespace Peter.Ftp.Tests.DataAccess
{
   /// <summary>
   /// Test cases for the FtpConnection class.
   /// </summary>
   [TestClass]
   public class FtpConnectionTests
   {
      private static FtpConnectionData s_Data;

      /// <summary>
      /// Tests the change directory command with an invalid name.
      /// </summary>
      [TestMethod]
      public async Task TestCwdInvalid ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsFalse (await connection.ChangeWorkingDirectory (null));
            Assert.IsFalse (await connection.ChangeWorkingDirectory (string.Empty));
            Assert.IsFalse (await connection.ChangeWorkingDirectory ("."));
         }
      }

      /// <summary>
      /// Verifies that a directory must be supplied to change the current directory.
      /// </summary>
      [TestMethod]
      public async Task TestCwdInvalidDir ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsFalse (await connection.ChangeWorkingDirectory ("/InvalidDir"));
         }
      }

      /// <summary>
      /// Verifies that a logged in user can change the current directory.
      /// </summary>
      [TestMethod]
      public async Task TestCwd ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsTrue (await connection.ChangeWorkingDirectory ("/TestDir"));
         }
      }

      /// <summary>
      /// Verifies that the login method can be called more than once.
      /// </summary>
      [TestMethod]
      public async Task TestDoubleLogin ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.AreEqual (connection.ConnectionData, ConnectionData);
            Assert.IsTrue (await connection.Connect (ConnectionData));
         }
      }

      /// <summary>
      /// Verifies that an exception will be thrown if an incorrect server is supplied.
      /// </summary>
      [TestMethod]
      public async Task TestInvalidServer ()
      {
         var data = ConnectionData.CloneProperties ();
         data.Server = "example.com";
         using (var connection = new FtpConnection { DoVerbose = true })
            Assert.IsFalse (await connection.Connect (data));
      }

      /// <summary>
      /// Verifies that an exception will be thrown if an incorrect user is supplied.
      /// </summary>
      [TestMethod]
      public async Task TestInvalidUser ()
      {
         var data = ConnectionData.CloneProperties ();
         data.UserName = "WrongUser";
         using (var connection = new FtpConnection { DoVerbose = true })
            Assert.IsFalse (await connection.Connect (data));
      }

      /// <summary>
      /// Verifies that an exception will be thrown if an incorrect password is supplied.
      /// </summary>
      [TestMethod]
      public async Task TestInvalidPassword ()
      {
         var data = ConnectionData.CloneProperties ();
         data.Password = "WrongPassword";
         using (var connection = new FtpConnection { DoVerbose = true })
            Assert.IsFalse (await connection.Connect (data));
      }

      /// <summary>
      /// Verifies the listing of files.
      /// </summary>
      [TestMethod]
      public async Task TestListFiles ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            var files = await connection.ListFiles ();
            Assert.AreEqual (5, files.Count (), "The number of listed files is incorrect.");
         }
      }

      /// <summary>
      /// Verifies that an unknown file will not crash the connection.
      /// </summary>
      [TestMethod]
      public async Task TestGetFileSizeInvalidFile ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            var size = await connection.GetFileSize ("TestFile");
            Assert.AreEqual (-1, size);
         }
      }

      /// <summary>
      /// Verifies the getting a file's size.
      /// </summary>
      [TestMethod]
      public async Task TestGetFileSize ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsTrue (await connection.ChangeWorkingDirectory ("TestDir"));
            var size = await connection.GetFileSize ("TestFile.txt");
            Assert.AreEqual (8, size, "File on Server is a different size than expected.");
         }
      }

      /// <summary>
      /// Verifies that the download method will not download a file that does not exist.
      /// </summary>
      [TestMethod]
      public async Task TestDownloadInvalidFile ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsFalse (await connection.DownloadFile ("TestFile", this.TestContext.TestDir + "\\FakeFile.txt", false));
         }
      }

      /// <summary>
      /// Verifies that the download method downloads a file.
      /// </summary>
      [TestMethod]
      public async Task TestDownload ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsTrue (await connection.ChangeWorkingDirectory ("TestDir"));
            Assert.IsTrue (await connection.DownloadFile ("TestFile.txt", this.TestContext.TestDir + "\\TestFile.txt", false));
            Assert.IsTrue (File.Exists (this.TestContext.TestDir + "\\TestFile.txt"));
            File.Delete (this.TestContext.TestDir + "\\TestFile.txt");
         }
      }

      /// <summary>
      /// Verifies that the upload method will not try to upload a file that does not exist locally.
      /// </summary>
      [TestMethod]
      public async Task TestUploadInvalidFile ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsFalse (await connection.UploadFile (this.TestContext.TestDir + "\\FakeUploadFile.txt", false));
         }
      }

      /// <summary>
      /// Verifies that the uplod method uploads a file.
      /// Verifies that delete file will delete the uploaded file.
      /// </summary>
      [TestMethod]
      public async Task TestUploadRenameDelete ()
      {
         var localFile = this.TestContext.TestDir + "\\UploadFile.txt";
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsTrue (await connection.ChangeWorkingDirectory ("TestDir"));
            File.WriteAllText (localFile, "Test Upload File");
            Assert.IsTrue (await connection.UploadFile (localFile, false));
            var fileInfo = new FileInfo (localFile);
            Assert.AreEqual (fileInfo.Length, await connection.GetFileSize ("UploadFile.txt"));
            fileInfo.Delete ();
            Assert.IsTrue (await connection.RenameFile ("UploadFile.txt", "UploadFile2.txt", true));
            Assert.AreEqual (fileInfo.Length, await connection.GetFileSize ("UploadFile2.txt"));
            Assert.IsTrue (await connection.DeleteFile ("UploadFile2.txt"));
         }
      }

      /// <summary>
      /// Tests the create directory command with an invalid name.
      /// </summary>
      [TestMethod]
      public async Task TestCreateInvalidDirectory ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsFalse (await connection.CreateDirectory (null));
            Assert.IsFalse (await connection.CreateDirectory (string.Empty));
            Assert.IsFalse (await connection.CreateDirectory ("."));
         }
      }

      /// <summary>
      /// Tests the remove directory command with an invalid name.
      /// </summary>
      [TestMethod]
      public async Task TestRemoveInvalidDirectory ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsFalse (await connection.RemoveDirectory (null));
            Assert.IsFalse (await connection.RemoveDirectory (string.Empty));
            Assert.IsFalse (await connection.RemoveDirectory ("."));
            Assert.IsFalse (await connection.RemoveDirectory ("NonExistantDir"));
         }
      }

      /// <summary>
      /// Tests the delete file command with an invalid name.
      /// </summary>
      [TestMethod]
      public async Task TestDeleteInvalidFile ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsFalse (await connection.DeleteFile ("NonExistantFile"));
         }
      }

      /// <summary>
      /// Tests the rename file command with an invalid data.
      /// </summary>
      [TestMethod]
      public async Task TestRenameInvalidFile ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsFalse (await connection.RenameFile ("NonExistantFile", "NonExistantFile", false));
            Assert.IsFalse (await connection.RenameFile ("NonExistantFile", "NonExistantFile2", false));
         }
      }

      /// <summary>
      /// Tests the rename file command with an invalid data.
      /// </summary>
      [TestMethod]
      public async Task TestRenameNoOverwrite ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsTrue (await connection.ChangeWorkingDirectory ("TestDir"));
            Assert.IsTrue (await connection.DownloadFile ("TestFile.txt", this.TestContext.TestDir + "\\TestFile.txt", false));
            File.Copy (this.TestContext.TestDir + "\\TestFile.txt", this.TestContext.TestDir + "\\TestFile2.txt");
            Assert.IsTrue (await connection.UploadFile (this.TestContext.TestDir + "\\TestFile2.txt", false));
            Assert.IsFalse (await connection.RenameFile ("TestFile.txt", "TestFile2.txt", false));
            Assert.IsTrue (await connection.DeleteFile ("TestFile2.txt"));
         }
      }

      /// <summary>
      /// Tests the create directory command with an existing directory.
      /// </summary>
      [TestMethod]
      public async Task TestCreateExistingDirectory ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsFalse (await connection.CreateDirectory ("TestDir"));
         }
      }

      /// <summary>
      /// Tests the create a remove directory commands.
      /// </summary>
      [TestMethod]
      public async Task TestCreateRemoveDirectory ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            Assert.IsTrue (await connection.Connect (ConnectionData));
            Assert.IsTrue (await connection.ChangeWorkingDirectory ("TestDir"));
            Assert.IsTrue (await connection.CreateDirectory ("TestCreate"));
            Assert.IsTrue (await connection.RemoveDirectory ("TestCreate"));
         }
      }

      /// <summary>
      /// Verifies that a connection is required to be connected for the given methods.
      /// </summary>
      [TestMethod]
      public async Task TestConnectionRequired ()
      {
         using (var connection = new FtpConnection { DoVerbose = true })
         {
            // We have not connected to any server at this point...
            Assert.IsFalse (await connection.ChangeWorkingDirectory ("TestDir"));
            Assert.IsFalse (await connection.CreateDirectory ("TestDir"));
            Assert.IsFalse (await connection.DeleteFile ("TestFile"));
            Assert.IsFalse (await connection.DownloadFile ("TestFile", "TestFile", false));
            Assert.IsFalse (await connection.RemoveDirectory ("TestDir"));
            Assert.IsFalse (await connection.RenameFile ("TestFile", "TestFile2", true));
            Assert.IsFalse (await connection.UploadFile ("TestFile", false));
            Assert.AreEqual (-1, await connection.GetFileSize ("TestFile"));
            Assert.IsNull (await connection.ListFiles ());

         }
      }

      /// <summary>
      /// Gets or Sets the test context which provides
      /// information about and functionality for the current test run.
      /// </summary>
      public TestContext TestContext { get; set; }

      /// <summary>
      /// Gets the common connection data.
      /// </summary>
      public static FtpConnectionData ConnectionData
      {
         get
         {
            return s_Data ?? (s_Data = new FtpConnectionData
            {
               Server = "ftp.seekdeepwithin.com",
               UserName = "ftptest",
               Password = "Xrf%65l7",
               Name = "TestSeekDeepWithin"
            });
         }
      }
   }
}
